import { Vec3 } from "./Vec3";

/*
 * @Author: Snow
 * @Date: 2021-09-15 17:18:10
 * @Description: 四维矩阵变换工具
 */
export class Matrix4 {
    public static create(): number[] {
        let out = [];
        out[0] = 1;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = 1;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = 1;
        out[11] = 0;
        out[12] = 0;
        out[13] = 0;
        out[14] = 0;
        out[15] = 1;
        return out;
    }

    public static copy(src: number[], dst?: number[]) {
        dst = dst || [];
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        dst[4] = src[4];
        dst[5] = src[5];
        dst[6] = src[6];
        dst[7] = src[7];
        dst[8] = src[8];
        dst[9] = src[9];
        dst[10] = src[10];
        dst[11] = src[11];
        dst[12] = src[12];
        dst[13] = src[13];
        dst[14] = src[14];
        dst[15] = src[15];
        return dst;
    }

    public static clone(a: number[]): number[] {
        let out = [];
        for (let index = 0; index < a.length; index++) {
            const element = a[index];
            out[index] = element;
        }
        return out;
    }

    public static identity(out?: number[]): number[] {
        out = out || [];
        out[0] = 1;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = 1;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = 1;
        out[11] = 0;
        out[12] = 0;
        out[13] = 0;
        out[14] = 0;
        out[15] = 1;
        return out;
    }

    /**
     * normalizes a vector.
     * @param {Vector3} v vector to normalize
     * @param {Vector3} dst optional vector3 to store result
     * @return {Vector3} dst or new Vector3 if not provided
     * @memberOf module:webgl-3d-math
     */
    public static normalize(v: number[], dst?: number[]) {
        dst = dst || [];
        var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
        // make sure we don't divide by 0.
        if (length > 0.00001) {
            dst[0] = v[0] / length;
            dst[1] = v[1] / length;
            dst[2] = v[2] / length;
        }
        return dst;
    }

    public static translation(tx: number, ty: number, tz: number): number[] {
        return [
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            tx, ty, tz, 1
        ];
    }

    // public static rotation(angleInRadians:number):number[] {
    //     let c = Math.cos(angleInRadians);
    //     let s = Math.sin(angleInRadians);
    //     return [
    //         c,-s, 0, 0,
    //         s, c, 0, 0,
    //         0, 0, 1, 0,
    //         0, 0, 0, 1
    //     ];
    // }


    /**
     * @Author: Snow
     * @description:  按照X轴旋转
     * @param {number} a
     * @return {*}
     */
    public static rotateX(a: number): number[] {
        // 绕Z轴旋转
        const rotateXMatrix = [
            1, 0, 0, 0,
            0, Math.cos(a), -Math.sin(a), 0,
            0, Math.sin(a), Math.cos(a), 0,
            0, 0, 0, 1
        ];
        return rotateXMatrix;
    }

    /**
     * @Author: Snow
     * @description: Y轴旋转
     * @param {number} a
     * @return {*}
     */
    public static rotateY(a: number): number[] {
        // 绕Z轴旋转
        const rotateYMatrix = [
            Math.cos(a), 0, Math.sin(a), 0,
            0, 1, 0, 0,
            -Math.sin(a), 0, Math.cos(a), 0,
            0, 0, 0, 1
        ];
        return rotateYMatrix;
    }

    /**
     * @Author: Snow
     * @description:  按照Z轴旋转
     * @param {number} a
     * @return {*}
     */
    public static rotateZ(a: number): number[] {
        // 绕Z轴旋转
        const rotateZMatrix = [
            Math.cos(a), -Math.sin(a), 0, 0,
            Math.sin(a), Math.cos(a), 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ];
        return rotateZMatrix;
    }

    public static scaling(sx: number, sy: number, sz: number): number[] {
        return [
            sx, 0, 0, 0,
            0, sy, 0, 0,
            0, 0, sz, 0,
            0, 0, 0, 1
        ];
    }

    /**
     * @Author: Snow
     * @description: 四维投影矩阵
     * @param {number} width
     * @param {number} height
     * @return {*}
     */
    public static projection(width: number, height: number, depth: number): number[] {
        // 注意：这个矩阵翻转了 Y 轴，所以 0 在上方
        return [
            2 / width, 0, 0, 0,
            0, -2 / height, 0, 0,
            0, 0, 2 / depth, 0,
            -1, 1, 0, 1
        ];
    }

    /**
     * @Author: Snow
     * @description: 四维投影矩阵
     * @param {number} left  左边
     * @param {number} right 右边
     * @param {number} bottom 底部
     * @param {number} top 上
     * @param {number} near 近
     * @param {number} far 远
     * @return {*}
     */
    public static orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): number[] {
        return [
            2 / (right - left), 0, 0, 0,
            0, 2 / (top - bottom), 0, 0,
            0, 0, 2 / (near - far), 0,

            (left + right) / (left - right),
            (bottom + top) / (bottom - top),
            (near + far) / (near - far),
            1,
        ];
    }

    /**
     * @Author: Snow
     * @description: 矩阵和向量相乘
     * @param {number} matrix
     * @param {number} vec4
     * @return {*}
     */
    public static multiplyPoint(matrix: number[], vec4: number[]): number[] {
        let out: number[] = [];
        for (let i = 0; i < 4; i++) {
            let num: number = 0
            for (let j = 0; j < 4; j++) {
                num += vec4[j] * matrix[i + j * 4];
            }
            out[i] = num;
        }
        return out;
    }

    // /**
    //  * Multiplies two mat3's
    //  *
    //  * @param {mat3} out the receiving matrix
    //  * @param {mat3} a the first operand
    //  * @param {mat3} b the second operand
    //  * @returns {mat3} out
    //  */
    // public static multiply(a: number[], b: number[]): number[] {
    //     let out: number[] = [];
    //     let a00 = a[0];
    //     let a01 = a[1];
    //     let a02 = a[2];
    //     let a03 = a[3];
    //     let a10 = a[4];
    //     let a11 = a[5];
    //     let a12 = a[6];
    //     let a13 = a[7];
    //     let a20 = a[8];
    //     let a21 = a[9];
    //     let a22 = a[10];
    //     let a23 = a[11];
    //     let a30 = a[12];
    //     let a31 = a[13];
    //     let a32 = a[14];
    //     let a33 = a[15];
    //     let b00 = b[0];
    //     let b01 = b[1];
    //     let b02 = b[2];
    //     let b03 = b[3];
    //     let b10 = b[4];
    //     let b11 = b[5];
    //     let b12 = b[6];
    //     let b13 = b[7];
    //     let b20 = b[8];
    //     let b21 = b[9];
    //     let b22 = b[10];
    //     let b23 = b[11];
    //     let b30 = b[12];
    //     let b31 = b[13];
    //     let b32 = b[14];
    //     let b33 = b[15];
    //     out[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
    //     out[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
    //     out[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
    //     out[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;

    //     out[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
    //     out[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
    //     out[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
    //     out[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;

    //     out[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
    //     out[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
    //     out[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
    //     out[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;

    //     out[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
    //     out[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
    //     out[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
    //     out[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;

    //     return out;
    // }

    public static multiply(a: number[] | Float32Array, b: number[] | Float32Array,dst?:number[] | Float32Array) {
        dst = dst || [];
        var b00 = b[0 * 4 + 0];
        var b01 = b[0 * 4 + 1];
        var b02 = b[0 * 4 + 2];
        var b03 = b[0 * 4 + 3];
        var b10 = b[1 * 4 + 0];
        var b11 = b[1 * 4 + 1];
        var b12 = b[1 * 4 + 2];
        var b13 = b[1 * 4 + 3];
        var b20 = b[2 * 4 + 0];
        var b21 = b[2 * 4 + 1];
        var b22 = b[2 * 4 + 2];
        var b23 = b[2 * 4 + 3];
        var b30 = b[3 * 4 + 0];
        var b31 = b[3 * 4 + 1];
        var b32 = b[3 * 4 + 2];
        var b33 = b[3 * 4 + 3];
        var a00 = a[0 * 4 + 0];
        var a01 = a[0 * 4 + 1];
        var a02 = a[0 * 4 + 2];
        var a03 = a[0 * 4 + 3];
        var a10 = a[1 * 4 + 0];
        var a11 = a[1 * 4 + 1];
        var a12 = a[1 * 4 + 2];
        var a13 = a[1 * 4 + 3];
        var a20 = a[2 * 4 + 0];
        var a21 = a[2 * 4 + 1];
        var a22 = a[2 * 4 + 2];
        var a23 = a[2 * 4 + 3];
        var a30 = a[3 * 4 + 0];
        var a31 = a[3 * 4 + 1];
        var a32 = a[3 * 4 + 2];
        var a33 = a[3 * 4 + 3];
        dst[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
        dst[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
        dst[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
        dst[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;
        dst[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
        dst[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
        dst[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
        dst[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;
        dst[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
        dst[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
        dst[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
        dst[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;
        dst[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
        dst[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
        dst[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
        dst[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;
        return dst as number[];
    }

    /**
     * @Author: Snow
     * @description: 
     * @param {number} out 输出矩阵
     * @param {number} fovy 
     * 弧度角度，指示观看者一层可以看多少场景。
     * 数字越大，摄像机可见的越多。边缘的几何形状变得越来越失真，等同于广角镜。
     * 当视野更大时，物体通常会变小。当视野较小时，摄像机在场景中的看到的东西会越来越少。
     * 物体因透视而变形的程度要小得多，并且物体似乎更靠近相机。

     * @param {number} aspect
      场景的宽高比，等于其宽度除以其高度。
      在本示例中，就是窗口的宽度除以窗口的高度。
      此参数的引入最终解决了当画布调整大小和形状时模型的变形问题。
     * @param {number} near 近景距离
      一个正数，表示到屏幕的距离是垂直于地板的平面的距离，该距离比将所有内容都裁剪的距离更近。
      它在裁剪空间中映射为-1，并且不应设置为0。
     * @param {number} far 远景距离
      一个正数，表示与平面之间的距离，超出该距离将裁剪几何体。
      它在裁剪空间中映射为1.
      该值应保持合理的距离以接近几何图形的距离，以免在渲染时出现精度误差。
     * @return {*}
     */
    public static perspective(fieldOfViewInRadians: number, aspect: number, near: number, far: number): number[] {
        let f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
        let rangeInv = 1.0 / (near - far);
        return [
            f / aspect, 0, 0, 0,
            0, f, 0, 0,
            0, 0, (near + far) * rangeInv, -1,
            0, 0, near * far * rangeInv * 2, 0
        ];
    }

    /**
     * @Author: Snow
     * @description: 逆矩阵
     * @param {*} a
     * @return {*}
     */
    public static invert(a: number[]): number[] {
        let out: number[] = [];
        let a00 = a[0];
        let a01 = a[1];
        let a02 = a[2];
        let a03 = a[3];
        let a10 = a[4];
        let a11 = a[5];
        let a12 = a[6];
        let a13 = a[7];
        let a20 = a[8];
        let a21 = a[9];
        let a22 = a[10];
        let a23 = a[11];
        let a30 = a[12];
        let a31 = a[13];
        let a32 = a[14];
        let a33 = a[15];
        let b00 = a00 * a11 - a01 * a10;
        let b01 = a00 * a12 - a02 * a10;
        let b02 = a00 * a13 - a03 * a10;
        let b03 = a01 * a12 - a02 * a11;
        let b04 = a01 * a13 - a03 * a11;
        let b05 = a02 * a13 - a03 * a12;
        let b06 = a20 * a31 - a21 * a30;
        let b07 = a20 * a32 - a22 * a30;
        let b08 = a20 * a33 - a23 * a30;
        let b09 = a21 * a32 - a22 * a31;
        let b10 = a21 * a33 - a23 * a31;
        let b11 = a22 * a33 - a23 * a32;
        // Calculate the determinant
        let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
        if (!det) {
            console.log("错误矩阵信息");
            return null;
        }
        det = 1.0 / det;
        out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
        out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
        out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
        out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
        out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
        out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
        out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
        out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
        out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
        out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
        out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
        out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
        out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
        out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
        out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
        out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
        return out;
    }

    /**
     * @Author: Snow
     * @description: 相机转换矩阵
     * @param {number} camaraPos
     * @param {number} target
     * @param {number} up
     * @return {*}
     */
    public static lookAt(camaraPos: number[], target: number[], up: number[]): number[] {
        let zAxis = Vec3.normalize(Vec3.subtract(camaraPos, target));
        let xAxis = Vec3.normalize(Vec3.cross(up, zAxis));
        let yAxis = Vec3.normalize(Vec3.cross(zAxis, xAxis));
        return [
            xAxis[0], xAxis[1], xAxis[2], 0,
            yAxis[0], yAxis[1], yAxis[2], 0,
            zAxis[0], zAxis[1], zAxis[2], 0,
            camaraPos[0],
            camaraPos[1],
            camaraPos[2],
            1,
        ];;
    }



    /**
     * @Author: Snow
     * @description: 矩阵转置
     * @param {number} m
     * @return {*}
     */
    public static transpose(m: number[]): number[] {
        return [
            m[0], m[4], m[8], m[12],
            m[1], m[5], m[9], m[13],
            m[2], m[6], m[10], m[14],
            m[3], m[7], m[11], m[15],
        ];
    }

    /**
    * @Author: Snow
    * @description: 矩阵旋转变换
    * @param {number} m
    * @param {number} v
    * @return {*}
    */
    public static transformPoint(m: number[], v: number[], des?: number[]): number[] {
        let dst = des || [];
        let v0 = v[0];
        let v1 = v[1];
        let v2 = v[2];
        let d = v0 * m[0 * 4 + 3] + v1 * m[1 * 4 + 3] + v2 * m[2 * 4 + 3] + m[3 * 4 + 3];

        dst[0] = (v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0] + m[3 * 4 + 0]) / d;
        dst[1] = (v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1] + m[3 * 4 + 1]) / d;
        dst[2] = (v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2] + m[3 * 4 + 2]) / d;

        return dst;
    }

    /**
   * Takes a 4-by-4 matrix and a vector with 3 entries, interprets the vector as a
   * direction, transforms that direction by the matrix, and returns the result;
   * assumes the transformation of 3-dimensional space represented by the matrix
   * is parallel-preserving, i.e. any combination of rotation, scaling and
   * translation, but not a perspective distortion. Returns a vector with 3
   * entries.
   * @param {Matrix4} m The matrix.
   * @param {Vector3} v The direction.
   * @param {Vector4} dst optional vector4 to store result
   * @return {Vector4} dst or new Vector4 if not provided
   * @memberOf module:webgl-3d-math
   */
    public static transformDirection(m: number[], v: number[], dst?: number[]) {
        dst = dst || [];

        var v0 = v[0];
        var v1 = v[1];
        var v2 = v[2];

        dst[0] = v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0];
        dst[1] = v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1];
        dst[2] = v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2];

        return dst;
    }

    /**
     * creates a matrix from translation, quaternion, scale
     * @param {Number[]} translation [x, y, z] translation
     * @param {Number[]} quaternion [x, y, z, z] quaternion rotation
     * @param {Number[]} scale [x, y, z] scale
     * @param {Matrix4} [dst] optional matrix to store result
     * @return {Matrix4} dst or a new matrix if none provided
     */
    public static compose(translation: number[], quaternion: number[], scale: number[], dst) {
        dst = dst || [];

        const x = quaternion[0];
        const y = quaternion[1];
        const z = quaternion[2];
        const w = quaternion[3];

        const x2 = x + x;
        const y2 = y + y;
        const z2 = z + z;

        const xx = x * x2;
        const xy = x * y2;
        const xz = x * z2;

        const yy = y * y2;
        const yz = y * z2;
        const zz = z * z2;

        const wx = w * x2;
        const wy = w * y2;
        const wz = w * z2;

        const sx = scale[0];
        const sy = scale[1];
        const sz = scale[2];

        dst[0] = (1 - (yy + zz)) * sx;
        dst[1] = (xy + wz) * sx;
        dst[2] = (xz - wy) * sx;
        dst[3] = 0;

        dst[4] = (xy - wz) * sy;
        dst[5] = (1 - (xx + zz)) * sy;
        dst[6] = (yz + wx) * sy;
        dst[7] = 0;

        dst[8] = (xz + wy) * sz;
        dst[9] = (yz - wx) * sz;
        dst[10] = (1 - (xx + yy)) * sz;
        dst[11] = 0;

        dst[12] = translation[0];
        dst[13] = translation[1];
        dst[14] = translation[2];
        dst[15] = 1;

        return dst;
    }

    
    private static quatFromRotationMatrix(m, dst) {
        // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
    
        // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
        const m11 = m[0];
        const m12 = m[4];
        const m13 = m[8];
        const m21 = m[1];
        const m22 = m[5];
        const m23 = m[9];
        const m31 = m[2];
        const m32 = m[6];
        const m33 = m[10];
    
        const trace = m11 + m22 + m33;
    
        if (trace > 0) {
          const s = 0.5 / Math.sqrt(trace + 1);
          dst[3] = 0.25 / s;
          dst[0] = (m32 - m23) * s;
          dst[1] = (m13 - m31) * s;
          dst[2] = (m21 - m12) * s;
        } else if (m11 > m22 && m11 > m33) {
          const s = 2 * Math.sqrt(1 + m11 - m22 - m33);
          dst[3] = (m32 - m23) / s;
          dst[0] = 0.25 * s;
          dst[1] = (m12 + m21) / s;
          dst[2] = (m13 + m31) / s;
        } else if (m22 > m33) {
          const s = 2 * Math.sqrt(1 + m22 - m11 - m33);
          dst[3] = (m13 - m31) / s;
          dst[0] = (m12 + m21) / s;
          dst[1] = 0.25 * s;
          dst[2] = (m23 + m32) / s;
        } else {
          const s = 2 * Math.sqrt(1 + m33 - m11 - m22);
          dst[3] = (m21 - m12) / s;
          dst[0] = (m13 + m31) / s;
          dst[1] = (m23 + m32) / s;
          dst[2] = 0.25 * s;
        }
      }
    
      /**
       * @Author: Snow
       * @description: 分解出各个值 
       * @param {*} mat
       * @param {*} translation
       * @param {*} quaternion
       * @param {*} scale
       * @return {*}
       */      
      public static decompose(mat, translation, quaternion, scale) {
        let sx = Vec3.len(mat.slice(0, 3));
        const sy = Vec3.len(mat.slice(4, 7));
        const sz = Vec3.len(mat.slice(8, 11));
    
        // if determinate is negative, we need to invert one scale
        const det = this.determinate(mat);
        if (det < 0) {
          sx = -sx;
        }
    
        translation[0] = mat[12];
        translation[1] = mat[13];
        translation[2] = mat[14];
    
        // scale the rotation part
        const matrix = Matrix4.copy(mat);
    
        const invSX = 1 / sx;
        const invSY = 1 / sy;
        const invSZ = 1 / sz;
    
        matrix[0] *= invSX;
        matrix[1] *= invSX;
        matrix[2] *= invSX;
    
        matrix[4] *= invSY;
        matrix[5] *= invSY;
        matrix[6] *= invSY;
    
        matrix[8] *= invSZ;
        matrix[9] *= invSZ;
        matrix[10] *= invSZ;
    
        this.quatFromRotationMatrix(matrix, quaternion);
    
        scale[0] = sx;
        scale[1] = sy;
        scale[2] = sz;
      }
    
      private static determinate(m) {
        var m00 = m[0 * 4 + 0];
        var m01 = m[0 * 4 + 1];
        var m02 = m[0 * 4 + 2];
        var m03 = m[0 * 4 + 3];
        var m10 = m[1 * 4 + 0];
        var m11 = m[1 * 4 + 1];
        var m12 = m[1 * 4 + 2];
        var m13 = m[1 * 4 + 3];
        var m20 = m[2 * 4 + 0];
        var m21 = m[2 * 4 + 1];
        var m22 = m[2 * 4 + 2];
        var m23 = m[2 * 4 + 3];
        var m30 = m[3 * 4 + 0];
        var m31 = m[3 * 4 + 1];
        var m32 = m[3 * 4 + 2];
        var m33 = m[3 * 4 + 3];
        var tmp_0  = m22 * m33;
        var tmp_1  = m32 * m23;
        var tmp_2  = m12 * m33;
        var tmp_3  = m32 * m13;
        var tmp_4  = m12 * m23;
        var tmp_5  = m22 * m13;
        var tmp_6  = m02 * m33;
        var tmp_7  = m32 * m03;
        var tmp_8  = m02 * m23;
        var tmp_9  = m22 * m03;
        var tmp_10 = m02 * m13;
        var tmp_11 = m12 * m03;
    
        var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
            (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
        var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
            (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
        var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
            (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
        var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
            (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
    
        return 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
      }
}
